/*! \file
 * \brief Enter a brief description here.
 *
 * Enter a detailed description here. (optional)
 * \author Author (Ivo Wingelaar)
 * \date Date (2011)
 * \version Version (0.0.1)
 * \copyright Copyright (GNU LGPL v3)
 */

#ifndef __LYNX_BLIT_H_INCLUDED__
#define __LYNX_BLIT_H_INCLUDED__

#include "LynxImage_C.hpp"

namespace Lynx
{

//! a more useful memset for pixel
inline void memset32(void* dest, const uint32_t value, uint32_t bytesize)
{
    uint32_t* d = (uint32_t*) dest;

    uint32_t i;

    i = bytesize >> (2 + 3);
    while(i)
    {
        d[0] = value;
        d[1] = value;
        d[2] = value;
        d[3] = value;

        d[4] = value;
        d[5] = value;
        d[6] = value;
        d[7] = value;

        d += 8;
        i -= 1;
    }

    i = (bytesize >> 2) & 7;
    while(i)
    {
        d[0] = value;
        d += 1;
        i -= 1;
    }

}

// integer log2 of a float ieee 754. TODO: non ieee floating point
static inline int32_t s32_log2_f32(float f)
{
    uint32_t x = *((uint32_t*)&f);
    return ((x & 0x7F800000) >> 23) - 127;
}

static inline int32_t s32_log2_s32(uint32_t x)
{
    return s32_log2_f32((float) x);
}

static inline int32_t s32_abs(int32_t x)
{
    int32_t b = x >> 31;
    return (x ^ b) - b;
}


//! conditional set based on mask and arithmetic shift
inline uint32_t if_mask_a_else_b(const uint32_t mask, const uint32_t a, const uint32_t b)
{
    return (mask & (a ^ b)) ^ b;
}

// ------------------ Video---------------------------------------
/*!
	Pixel = dest * ( 1 - alpha ) + source * alpha
	alpha [0;256]
*/
inline uint32_t PixelBlend32(const uint32_t c2, const uint32_t c1, uint32_t alpha)
{
    uint32_t srcRB = c1 & 0x00FF00FF;
    uint32_t srcXG = c1 & 0x0000FF00;

    uint32_t dstRB = c2 & 0x00FF00FF;
    uint32_t dstXG = c2 & 0x0000FF00;


    uint32_t rb = srcRB - dstRB;
    uint32_t xg = srcXG - dstXG;

    rb *= alpha;
    xg *= alpha;
    rb >>= 8;
    xg >>= 8;

    rb += dstRB;
    xg += dstXG;

    rb &= 0x00FF00FF;
    xg &= 0x0000FF00;

    return rb | xg;
}

/*!
	Pixel = dest * ( 1 - alpha ) + source * alpha
	alpha [0;32]
*/
inline int16_t PixelBlend16(const int16_t c2, const uint32_t c1, const int16_t alpha)
{
    const int16_t srcRB = c1 & 0x7C1F;
    const int16_t srcXG = c1 & 0x03E0;

    const int16_t dstRB = c2 & 0x7C1F;
    const int16_t dstXG = c2 & 0x03E0;

    uint32_t rb = srcRB - dstRB;
    uint32_t xg = srcXG - dstXG;

    rb *= alpha;
    xg *= alpha;
    rb >>= 5;
    xg >>= 5;

    rb += dstRB;
    xg += dstXG;

    rb &= 0x7C1F;
    xg &= 0x03E0;

    return (int16_t)(rb | xg);
}

inline int16_t PixelMul16_2(int16_t c0, int16_t c1)
{
    return	(int16_t)((((c0 & 0x7C00) * (c1 & 0x7C00)) & 0x3E000000) >> 15 |
                      (((c0 & 0x03E0) * (c1 & 0x03E0)) & 0x000F8000) >> 10 |
                      (((c0 & 0x001F) * (c1 & 0x001F)) & 0x000003E0) >> 5  |
                      (c0 & c1 & 0x8000));
}

inline uint32_t PixelMul32_2(const uint32_t c0, const uint32_t c1)
{
    return	((((c0 & 0xFF000000) >> 16) * ((c1 & 0xFF000000) >> 16)) & 0xFF000000) |
            ((((c0 & 0x00FF0000) >> 12) * ((c1 & 0x00FF0000) >> 12)) & 0x00FF0000) |
            ((((c0 & 0x0000FF00) * (c1 & 0x0000FF00)) >> 16) & 0x0000FF00) |
            ((((c0 & 0x000000FF) * (c1 & 0x000000FF)) >> 8) & 0x000000FF);
}

inline int16_t PixelBlend16(const int16_t c2, const int16_t c1)
{
    int16_t mask = ((c1 & 0x8000) >> 15) + 0x7fff;
    return (c2 & mask) | (c1 & ~mask);
}

inline uint32_t PixelBlend16_simd(const uint32_t c2, const uint32_t c1)
{
    uint32_t mask = ((c1 & 0x80008000) >> 15) + 0x7fff7fff;
    return (c2 & mask) | (c1 & ~mask);
}

inline uint32_t PixelBlend32(const uint32_t c2, const uint32_t c1)
{
    // alpha test
    uint32_t alpha = c1 & 0xFF000000;

    if(0 == alpha)
        return c2;

    if(0xFF000000 == alpha)
    {
        return c1;
    }

    alpha >>= 24;

    // add highbit alpha, if ( alpha > 127 ) alpha += 1;
    alpha += (alpha >> 7);

    uint32_t srcRB = c1 & 0x00FF00FF;
    uint32_t srcXG = c1 & 0x0000FF00;

    uint32_t dstRB = c2 & 0x00FF00FF;
    uint32_t dstXG = c2 & 0x0000FF00;


    uint32_t rb = srcRB - dstRB;
    uint32_t xg = srcXG - dstXG;

    rb *= alpha;
    xg *= alpha;
    rb >>= 8;
    xg >>= 8;

    rb += dstRB;
    xg += dstXG;

    rb &= 0x00FF00FF;
    xg &= 0x0000FF00;

    return (c1 & 0xFF000000) | rb | xg;
}

// some 2D Defines
struct AbsRectangle
{
    int32_t x0;
    int32_t y0;
    int32_t x1;
    int32_t y1;
};

//! 2D Intersection test
inline bool intersect(AbsRectangle& dest, const AbsRectangle& a, const AbsRectangle& b)
{
    dest.x0 = Myth::Math::max(a.x0, b.x0);
    dest.y0 = Myth::Math::max(a.y0, b.y0);
    dest.x1 = Myth::Math::min(a.x1, b.x1);
    dest.y1 = Myth::Math::min(a.y1, b.y1);
    return dest.x0 < dest.x1 && dest.y0 < dest.y1;
}


struct SBlitJob
{
    AbsRectangle Dest;
    AbsRectangle Source;

    uint32_t argb;

    void* src;
    void* dst;

    int32_t width;
    int32_t height;

    uint32_t srcPitch;
    uint32_t dstPitch;

    uint32_t srcPixelMul;
    uint32_t dstPixelMul;
};


// Bitfields Cohen Sutherland
enum eClipCode
{
    CLIPCODE_EMPTY	=	0,
    CLIPCODE_BOTTOM	=	1,
    CLIPCODE_TOP	=	2,
    CLIPCODE_LEFT	=	4,
    CLIPCODE_RIGHT	=	8
};

inline uint32_t GetClipCode(const AbsRectangle& r, const Myth::Math::Vector2i& p)
{
    uint32_t code = CLIPCODE_EMPTY;

    if(p.X < r.x0)
        code = CLIPCODE_LEFT;
    else if(p.X > r.x1)
        code = CLIPCODE_RIGHT;

    if(p.Y < r.y0)
        code |= CLIPCODE_TOP;
    else if(p.Y > r.y1)
        code |= CLIPCODE_BOTTOM;

    return code;
}


/*
*/
inline void GetClip(AbsRectangle& clipping, Image* t)
{
    clipping.x0 = 0;
    clipping.y0 = 0;
    clipping.x1 = t->get_dimension().X - 1;
    clipping.y1 = t->get_dimension().Y - 1;
}

/*
	return alpha in [0;256] Granularity from 32-Bit ARGB
	add highbit alpha ( alpha > 127 ? + 1 )
*/
static inline uint32_t extractAlpha(const uint32_t c)
{
    return (c >> 24) + (c >> 31);
}

/*
	return alpha in [0;255] Granularity and 32-Bit ARGB
	add highbit alpha ( alpha > 127 ? + 1 )
*/
static inline uint32_t packAlpha(const uint32_t c)
{
    return (c > 127 ? c - 1 : c) << 24;
}


/*!
	Scale Color by (1/value)
	value 0 - 256 ( alpha )
*/
inline uint32_t PixelLerp32(const uint32_t source, const uint32_t value)
{
    uint32_t srcRB = source & 0x00FF00FF;
    uint32_t srcXG = (source & 0xFF00FF00) >> 8;

    srcRB *= value;
    srcXG *= value;

    srcRB >>= 8;
    //srcXG >>= 8;

    srcXG &= 0xFF00FF00;
    srcRB &= 0x00FF00FF;

    return srcRB | srcXG;
}


/*!
*/
static void executeBlit_TextureCopy_x_to_x(const SBlitJob* job)
{
    const void* src = (void*) job->src;
    void* dst = (void*) job->dst;

    const uint32_t widthPitch = job->width * job->dstPixelMul;
    for(int32_t dy = 0; dy != job->height; ++dy)
    {
        memcpy(dst, src, widthPitch);

        src = (void*)((uint8_t*)(src) + job->srcPitch);
        dst = (void*)((uint8_t*)(dst) + job->dstPitch);
    }
}


/*!
*/
static void executeBlit_TextureCopy_32_to_16(const SBlitJob* job)
{
    const uint32_t* src = static_cast<const uint32_t*>(job->src);
    uint16_t* dst = static_cast<uint16_t*>(job->dst);

    for(int32_t dy = 0; dy != job->height; ++dy)
    {
        for(int32_t dx = 0; dx != job->width; ++dx)
        {
            //16 bit Blitter depends on pre-multiplied color
            const uint32_t s = PixelLerp32(src[dx] | 0xFF000000, extractAlpha(src[dx]));
            dst[dx] = a8r8g8b8_to_a1r5g5b5(s);
        }

        src = (uint32_t*)((uint8_t*)(src) + job->srcPitch);
        dst = (uint16_t*)((uint8_t*)(dst) + job->dstPitch);
    }
}

/*!
*/
static void executeBlit_TextureCopy_24_to_16(const SBlitJob* job)
{
    const void* src = (void*) job->src;
    uint16_t* dst = (uint16_t*) job->dst;

    for(int32_t dy = 0; dy != job->height; ++dy)
    {
        uint8_t* s = (uint8_t*) src;

        for(int32_t dx = 0; dx != job->width; ++dx)
        {
            dst[dx] = rgba16(s[0], s[1], s[2]);
            s += 3;
        }

        src = (void*)((uint8_t*)(src) + job->srcPitch);
        dst = (uint16_t*)((uint8_t*)(dst) + job->dstPitch);
    }
}


/*!
*/
static void executeBlit_TextureCopy_16_to_32(const SBlitJob* job)
{
    const uint16_t* src = (uint16_t*) job->src;
    uint32_t* dst = (uint32_t*) job->dst;

    for(int32_t dy = 0; dy != job->height; ++dy)
    {
        for(int32_t dx = 0; dx != job->width; ++dx)
        {
            dst[dx] = a1r5g5b5_to_a8r8g8b8(src[dx]);
        }

        src = (uint16_t*)((uint8_t*)(src) + job->srcPitch);
        dst = (uint32_t*)((uint8_t*)(dst) + job->dstPitch);
    }
}

static void executeBlit_TextureCopy_16_to_24(const SBlitJob* job)
{
    const uint16_t* src = (uint16_t*) job->src;
    uint8_t* dst = (uint8_t*) job->dst;

    for(int32_t dy = 0; dy != job->height; ++dy)
    {
        for(int32_t dx = 0; dx != job->width; ++dx)
        {
            uint32_t colour = a1r5g5b5_to_a8r8g8b8(src[dx]);
            uint8_t* writeTo = &dst[dx * 3];
            *writeTo++ = (colour >> 16)& 0xFF;
            *writeTo++ = (colour >> 8) & 0xFF;
            *writeTo++ = colour & 0xFF;
        }

        src = (uint16_t*)((uint8_t*)(src) + job->srcPitch);
        dst += job->dstPitch;
    }
}


/*!
*/
static void executeBlit_TextureCopy_24_to_32(const SBlitJob* job)
{
    void* src = (void*) job->src;
    uint32_t* dst = (uint32_t*) job->dst;

    for(int32_t dy = 0; dy != job->height; ++dy)
    {
        uint8_t* s = (uint8_t*) src;

        for(int32_t dx = 0; dx != job->width; ++dx)
        {
            dst[dx] = 0xFF000000 | s[0] << 16 | s[1] << 8 | s[2];
            s += 3;
        }

        src = (void*)((uint8_t*)(src) + job->srcPitch);
        dst = (uint32_t*)((uint8_t*)(dst) + job->dstPitch);
    }
}

static void executeBlit_TextureCopy_32_to_24(const SBlitJob* job)
{
    const uint32_t* src = (uint32_t*) job->src;
    uint8_t* dst = (uint8_t*) job->dst;

    for(int32_t dy = 0; dy != job->height; ++dy)
    {
        for(int32_t dx = 0; dx != job->width; ++dx)
        {
            uint8_t* writeTo = &dst[dx * 3];
            *writeTo++ = (src[dx] >> 16)& 0xFF;
            *writeTo++ = (src[dx] >> 8) & 0xFF;
            *writeTo++ = src[dx] & 0xFF;
        }

        src = (uint32_t*)((uint8_t*)(src) + job->srcPitch);
        dst += job->dstPitch ;
    }

}


/*!
*/
static void executeBlit_TextureBlend_16_to_16(const SBlitJob* job)
{
    uint32_t dx;
    int32_t dy;

    uint32_t* src = (uint32_t*) job->src;
    uint32_t* dst = (uint32_t*) job->dst;


    const uint32_t rdx = job->width >> 1;
    const uint32_t off = Myth::Math::if_c_a_else_b(job->width & 1 ,job->width - 1, 0);


    for(dy = 0; dy != job->height; ++dy)
    {
        for(dx = 0; dx != rdx; ++dx)
        {
            dst[dx] = PixelBlend16_simd(dst[dx], src[dx]);
        }

        if(off)
        {
            ((uint16_t*) dst)[off] = PixelBlend16(((uint16_t*) dst)[off], ((uint16_t*) src)[off]);
        }

        src = (uint32_t*)((uint8_t*)(src) + job->srcPitch);
        dst = (uint32_t*)((uint8_t*)(dst) + job->dstPitch);
    }
}

/*!
*/
static void executeBlit_TextureBlend_32_to_32(const SBlitJob* job)
{
    uint32_t* src = (uint32_t*) job->src;
    uint32_t* dst = (uint32_t*) job->dst;

    for(int32_t dy = 0; dy != job->height; ++dy)
    {
        for(int32_t dx = 0; dx != job->width; ++dx)
        {
            dst[dx] = PixelBlend32(dst[dx], src[dx]);
        }
        src = (uint32_t*)((uint8_t*)(src) + job->srcPitch);
        dst = (uint32_t*)((uint8_t*)(dst) + job->dstPitch);
    }
}

/*!
*/
static void executeBlit_TextureBlendColor_16_to_16(const SBlitJob* job)
{
    uint16_t* src = (uint16_t*) job->src;
    uint16_t* dst = (uint16_t*) job->dst;

    uint16_t blend = a8r8g8b8_to_a1r5g5b5(job->argb);
    for(int32_t dy = 0; dy != job->height; ++dy)
    {
        for(int32_t dx = 0; dx != job->width; ++dx)
        {
            if(0 == (src[dx] & 0x8000))
                continue;

            dst[dx] = PixelMul16_2(src[dx], blend);
        }
        src = (uint16_t*)((uint8_t*)(src) + job->srcPitch);
        dst = (uint16_t*)((uint8_t*)(dst) + job->dstPitch);
    }
}


/*!
*/
static void executeBlit_TextureBlendColor_32_to_32(const SBlitJob* job)
{
    uint32_t* src = (uint32_t*) job->src;
    uint32_t* dst = (uint32_t*) job->dst;

    for(int32_t dy = 0; dy != job->height; ++dy)
    {
        for(int32_t dx = 0; dx != job->width; ++dx)
        {
            dst[dx] = PixelBlend32(dst[dx], PixelMul32_2(src[dx], job->argb));
        }
        src = (uint32_t*)((uint8_t*)(src) + job->srcPitch);
        dst = (uint32_t*)((uint8_t*)(dst) + job->dstPitch);
    }
}

// Blitter Operation
enum eBlitter
{
    BLITTER_INVALID = 0,
    BLITTER_COLOR,
    BLITTER_COLOR_ALPHA,
    BLITTER_TEXTURE,
    BLITTER_TEXTURE_ALPHA_BLEND,
    BLITTER_TEXTURE_ALPHA_COLOR_BLEND
};

typedef void (*tExecuteBlit)(const SBlitJob* job);


/*!
*/
struct blitterTable
{
    eBlitter operation;
    int32_t destFormat;
    int32_t sourceFormat;
    tExecuteBlit func;
};

static const blitterTable blitTable[] =
{
    { BLITTER_TEXTURE, -2, -2, executeBlit_TextureCopy_x_to_x },
    { BLITTER_TEXTURE, ECF_A1R5G5B5, ECF_A8R8G8B8, executeBlit_TextureCopy_32_to_16 },
    { BLITTER_TEXTURE, ECF_A1R5G5B5, ECF_R8G8B8, executeBlit_TextureCopy_24_to_16 },
    { BLITTER_TEXTURE, ECF_A8R8G8B8, ECF_A1R5G5B5, executeBlit_TextureCopy_16_to_32 },
    { BLITTER_TEXTURE, ECF_A8R8G8B8, ECF_R8G8B8, executeBlit_TextureCopy_24_to_32 },
    { BLITTER_TEXTURE, ECF_R8G8B8, ECF_A1R5G5B5, executeBlit_TextureCopy_16_to_24 },
    { BLITTER_TEXTURE, ECF_R8G8B8, ECF_A8R8G8B8, executeBlit_TextureCopy_32_to_24 },
    { BLITTER_TEXTURE_ALPHA_BLEND, ECF_A1R5G5B5, ECF_A1R5G5B5, executeBlit_TextureBlend_16_to_16 },
    { BLITTER_TEXTURE_ALPHA_BLEND, ECF_A8R8G8B8, ECF_A8R8G8B8, executeBlit_TextureBlend_32_to_32 },
    { BLITTER_TEXTURE_ALPHA_COLOR_BLEND, ECF_A1R5G5B5, ECF_A1R5G5B5, executeBlit_TextureBlendColor_16_to_16 },
    { BLITTER_TEXTURE_ALPHA_COLOR_BLEND, ECF_A8R8G8B8, ECF_A8R8G8B8, executeBlit_TextureBlendColor_32_to_32 },
    /*	{ BLITTER_COLOR, ECF_A1R5G5B5, -1, executeBlit_Color_16_to_16 },
    	{ BLITTER_COLOR, ECF_A8R8G8B8, -1, executeBlit_Color_32_to_32 },
    	{ BLITTER_COLOR_ALPHA, ECF_A1R5G5B5, -1, executeBlit_ColorAlpha_16_to_16 },
    	{ BLITTER_COLOR_ALPHA, ECF_A8R8G8B8, -1, executeBlit_ColorAlpha_32_to_32 },*/
    { BLITTER_INVALID, -1, -1, 0 }
};


static inline tExecuteBlit getBlitter2(eBlitter operation,const Image* dest,const Image* source)
{
    E_COLOR_FORMAT sourceFormat = (E_COLOR_FORMAT)(source ? source->get_color_format() : -1);
    E_COLOR_FORMAT destFormat = (E_COLOR_FORMAT)(dest ? dest->get_color_format() : -1);

    const blitterTable* b = blitTable;

    while(b->operation != BLITTER_INVALID)
    {
        if(b->operation == operation)
        {
            if((b->destFormat == -1 || b->destFormat == destFormat) &&
                    (b->sourceFormat == -1 || b->sourceFormat == sourceFormat))
                return b->func;
            else if(b->destFormat == -2 && (sourceFormat == destFormat))
                return b->func;
        }
        b += 1;
    }
    return 0;
}


// bounce clipping to texture
inline void setClip(AbsRectangle& out, const Myth::Math::Rectanglei* clip,
                    const Image* tex, int32_t passnative)
{
    if(clip && 0 == tex && passnative)
    {
        out.x0 = clip->UpperLeftCorner.X;
        out.x1 = clip->LowerRightCorner.X;
        out.y0 = clip->UpperLeftCorner.Y;
        out.y1 = clip->LowerRightCorner.Y;
        return;
    }

    const int32_t w = tex ? tex->get_dimension().X : 0;
    const int32_t h = tex ? tex->get_dimension().Y : 0;
    if(clip)
    {
        out.x0 = Myth::Math::clamp(clip->UpperLeftCorner.X, 0, w);
        out.x1 = Myth::Math::clamp(clip->LowerRightCorner.X, out.x0, w);
        out.y0 = Myth::Math::clamp(clip->UpperLeftCorner.Y, 0, h);
        out.y1 = Myth::Math::clamp(clip->LowerRightCorner.Y, out.y0, h);
    }
    else
    {
        out.x0 = 0;
        out.y0 = 0;
        out.x1 = w;
        out.y1 = h;
    }

}

/*!
	a generic 2D Blitter
*/
static int32_t Blit(eBlitter operation,
                    Image* dest,
                    const Myth::Math::Rectanglei* destClipping,
                    const Myth::Math::Vector2i* destPos,
                    Image* const source,
                    const Myth::Math::Rectanglei* sourceClipping,
                    uint32_t argb)
{
    tExecuteBlit blitter = getBlitter2(operation, dest, source);
    if(0 == blitter)
    {
        return 0;
    }

    // Clipping
    AbsRectangle sourceClip;
    AbsRectangle destClip;
    AbsRectangle v;

    SBlitJob job;

    setClip(sourceClip, sourceClipping, source, 1);
    setClip(destClip, destClipping, dest, 0);

    v.x0 = destPos ? destPos->X : 0;
    v.y0 = destPos ? destPos->Y : 0;
    v.x1 = v.x0 + (sourceClip.x1 - sourceClip.x0);
    v.y1 = v.y0 + (sourceClip.y1 - sourceClip.y0);

    if(!intersect(job.Dest, destClip, v))
        return 0;

    job.width = job.Dest.x1 - job.Dest.x0;
    job.height = job.Dest.y1 - job.Dest.y0;


    job.Source.x0 = sourceClip.x0 + (job.Dest.x0 - v.x0);
    job.Source.x1 = job.Source.x0 + job.width;

    job.Source.y0 = sourceClip.y0 + (job.Dest.y0 - v.y0);
    job.Source.y1 = job.Source.y0 + job.height;

    job.argb = argb;

    if(source)
    {
        job.srcPitch = source->get_pitch();
        job.srcPixelMul = source->get_bytes_per_pixel();
        job.src = (void*)((uint8_t*) source->get_pointer() + (job.Source.y0 * job.srcPitch) + (job.Source.x0 * job.srcPixelMul));
    }
    else
    {
        // use srcPitch for color operation on dest
        job.srcPitch = job.width * dest->get_bytes_per_pixel();
    }

    job.dstPitch = dest->get_pitch();
    job.dstPixelMul = dest->get_bytes_per_pixel();
    job.dst = (void*)((uint8_t*) dest->get_pointer() + (job.Dest.y0 * job.dstPitch) + (job.Dest.x0 * job.dstPixelMul));

    blitter(&job);

    return 1;
}

} // namespace Lynx

#endif // __LYNX_BLIT_H_INCLUDED__

